﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Kira.Compiler.Bods
{
    /// <summary>
    /// wrapper of XElement, only expose some readonly interfaces, and simplify part of them
    /// </summary>
    public abstract class BodsElementBase
    {
        private XElement _xml;
        internal XElement Xml
        {
            get
            {
                return _xml;
            }
            set
            {
                if (_xml == null)
                {
                    _xml = value;
                }
                else
                {
                    throw new Exception("Property[Xml] can't be set twice");
                }
            }
        }

        private BodsElementBase _parent;

        internal BodsElementBase Parent
        {
            get { return _parent; }
            set
            {
                if (_parent == null)
                {
                    _parent = value;
                }
                else
                {
                    throw new Exception("Property[Parent] can't be set twice");
                }
            }
        }

        public String ElementName
        {
            get { return Xml.Name.ToString(); }
        }

        public bool HasAttributes
        {
            get { return this.Attributes.Count() > 0; }
        }

        public bool HasElements
        {
            get { return this.Elements.Count() > 0; }
        }

        public bool IsEmpty
        {
            get { return Xml.IsEmpty && this.HasElements; }
        }

        public String Value
        {
            get { return Xml.Value; }
        }

        private List<BodsElementAttribute> _attributes;
        private void initAttributes()
        {
            if (_attributes == null)
            {
                _attributes = (from attr in Xml.Attributes()
                               select new BodsElementAttribute()
                               {
                                   Name = attr.Name.LocalName,
                                   Value = attr.Value
                               }).ToList();
            }
        }
        public IEnumerable<BodsElementAttribute> Attributes
        {
            get
            {
                initAttributes();
                return _attributes;
            }
        }

        public bool HasAttribute(String attrName)
        {
            return this.Attributes.Any(c => String.Compare(c.Name, attrName, true) == 0);
        }

        public String GetAttrbuteValue(String attrName)
        {
            return this.Attributes.FirstOrDefault(c => String.Compare(c.Name, attrName, true) == 0).Value;
        }

        public int GetAttrbuteIntegerValue(String attrName)
        {
            return int.Parse(this.GetAttrbuteValue(attrName));
        }

        private List<BodsElementBase> _elements;

        private void initElements()
        {
            if (_elements == null)
            {
                _elements = (from ele in Xml.Elements()
                             select BodsElementFactory.Create(ele)).ToList();
            }
        }
        public virtual IEnumerable<BodsElementBase> Elements
        {
            get
            {
                initElements();
                return _elements;
            }
        }

        public BodsElementBase FirstElement
        {
            get
            {
                return (this.Elements as List<BodsElementBase>)[0];
            }
        }

        public BodsElementBase LastElement
        {
            get
            {
                var lst = this.Elements as List<BodsElementBase>;
                return lst[lst.Count - 1];
            }
        }

        public bool HasElement(String name)
        {
            return this.Elements.Any(c => String.Compare(c.ElementName, name, true) == 0);
        }

        public BodsElementBase GetElement(String name)
        {
            return this.Elements.FirstOrDefault(c => String.Compare(c.ElementName, name, true) == 0);
        }

        public IEnumerable<BodsElementBase> GetElements(String name)
        {
            return this.Elements.Where(c => String.Compare(c.ElementName, name, true) == 0);
        }

        public TElement GetElement<TElement>() where TElement : BodsElementBase
        {
            return this.Elements.FirstOrDefault(c => typeof(TElement).IsAssignableFrom(c.GetType())) as TElement;
        }

        public IEnumerable<TElement> GetElements<TElement>() where TElement : BodsElementBase
        {
            return from c in this.Elements.Where(c => typeof(TElement).IsAssignableFrom(c.GetType())) select c as TElement;
        }

        public BodsElementBase GetElement(Func<BodsElementBase, bool> predicate)
        {
            return this.Elements.FirstOrDefault(predicate);
        }

        public IEnumerable<BodsElementBase> GetElements(Func<BodsElementBase, bool> predicate)
        {
            return this.Elements.Where(predicate);
        }

        public override string ToString()
        {
            return this.Xml.ToString();
        }
    }
}
